package com.itcam.cammydada.scoring;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.itcam.cammydada.manager.AiManager;
import com.itcam.cammydada.model.dto.question.QuestionAnswerDTO;
import com.itcam.cammydada.model.dto.question.QuestionContentDTO;
import com.itcam.cammydada.model.entity.App;
import com.itcam.cammydada.model.entity.Question;
import com.itcam.cammydada.model.entity.UserAnswer;
import com.itcam.cammydada.model.vo.QuestionVO;
import com.itcam.cammydada.service.QuestionService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Author:Cammy.Wu
 * Date:2024-07-10
 * Description:AI 测评类应用评分策略
 */

@ScoringStrategyConfiguration(appType = 1, scoringStrategy = 1)
public class AiTestScoringStrategy implements ScoringStrategy {

    @Resource
    private QuestionService questionService;

    @Resource
    private AiManager aiManager;

    @Resource
    private RedissonClient redissonClient;

    // 分布式锁的 key
    private static final String AI_ANSWER_LOCK = "AI_ANSWER_LOCK";

    /**
     * AI 评分结果本地缓存
     */
    private final Cache<String, String> answerCacheMap =
            Caffeine.newBuilder().initialCapacity(1024)
                    // 缓存五分钟移除
                    .expireAfterWrite(5, TimeUnit.MINUTES)
                    .build();

    /**
     * AI 评分系统消息
     */
    private static final String AI_TEST_SCORING_SYSTEM_MESSAGE = "你是一位严谨的判题专家，我会给你如下信息：\n" +
            "```\n" +
            "应用名称，\n" +
            "【【【应用描述】】】，\n" +
            "题目和用户回答的列表：格式为 [{\"title\": \"题目\",\"answer\": \"用户回答\"}]\n" +
            "```\n" +
            "\n" +
            "请你根据上述信息，按照以下步骤来对用户进行评价：\n" +
            "1. 要求：需要给出一个明确的评价结果，包括评价名称（尽量简短）和评价描述（尽量详细，大于 200 字）\n" +
            "2. 严格按照下面的 json 格式输出评价名称和评价描述\n" +
            "```\n" +
            "{\"resultName\": \"评价名称\", \"resultDesc\": \"评价描述\"}\n" +
            "```\n" +
            "3. 返回格式必须为 JSON 对象";


    /**
     * 评分
     *
     * @param choices
     * @param app
     * @return
     * @throws Exception
     */
    @Override
    public UserAnswer doScore(List<String> choices, App app) throws Exception {
        // 获取应用程序的 ID
        Long appId = app.getId();
        // 将用户的选择列表转换为 JSON 字符串
        String jsonStr = JSONUtil.toJsonStr(choices);
        // 根据应用程序 ID 和用户选择的 JSON 字符串构建缓存键
        String cacheKey = buildCacheKey(appId, jsonStr);
        // 尝试从缓存中获取评分结果的 JSON 字符串
        String answerJson = answerCacheMap.getIfPresent(cacheKey);
        // 如果缓存中有评分结果，则直接返回
        if (StrUtil.isNotBlank(answerJson)) {
            // 将评分结果的 JSON 字符串转换为 UserAnswer 对象
            UserAnswer userAnswer = JSONUtil.toBean(answerJson, UserAnswer.class);
            // 设置 UserAnswer 对象的应用程序 ID、应用程序类型和评分策略
            userAnswer.setAppId(appId);
            userAnswer.setAppType(app.getAppType());
            userAnswer.setScoringStrategy(app.getScoringStrategy());
            // 设置 UserAnswer 对象的用户选择 JSON 字符串
            userAnswer.setChoices(jsonStr);
            // 返回评分结果
            return userAnswer;
        }

        // 定义分布式锁
        RLock lock = redissonClient.getLock(AI_ANSWER_LOCK + cacheKey);

        try {
            // 尝试获取分布式锁，最多等待 3 秒，持有锁的时间为 15 秒
            boolean res = lock.tryLock(3, 15, TimeUnit.SECONDS);
            // 如果没有获取到锁，则强行返回
            if (!res) {
                return null;
            }

            // 获取到锁后，执行后续业务逻辑

            // 1. 根据应用程序 ID 查询问题
            Question question = questionService.getOne(
                    Wrappers.lambdaQuery(Question.class).eq(Question::getAppId, appId)
            );
            // 将问题对象转换为 QuestionVO 对象
            QuestionVO questionVO = QuestionVO.objToVo(question);
            // 获取问题内容列表
            List<QuestionContentDTO> questionContent = questionVO.getQuestionContent();

            // 2. 调用 AI 获取评分结果
            // 封装用户消息
            String userMessage = getAiTestScoringUserMessage(app, questionContent, choices);
            // 调用 AI 管理器进行同步稳定请求
            String result = aiManager.doSyncStableRequest(AI_TEST_SCORING_SYSTEM_MESSAGE, userMessage);
            // 截取需要的 JSON 信息
            int start = result.indexOf("{");
            int end = result.lastIndexOf("}");
            String json = result.substring(start, end + 1);

            // 3. 构造返回值，填充答案对象的属性
            UserAnswer userAnswer = JSONUtil.toBean(json, UserAnswer.class);
            userAnswer.setAppId(appId);
            userAnswer.setAppType(app.getAppType());
            userAnswer.setScoringStrategy(app.getScoringStrategy());
            userAnswer.setChoices(jsonStr);
            return userAnswer;
        } finally {
            // 释放分布式锁
            if (lock != null && lock.isLocked()) {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
    }


    /**
     * AI 评分用户消息封装
     *
     * @param app 应用程序对象
     * @param questionContentDTOList 问题内容 DTO 列表
     * @param choices 用户选择列表
     * @return 构建的用户消息字符串
     */
    private String getAiTestScoringUserMessage(App app, List<QuestionContentDTO> questionContentDTOList, List<String> choices) {
        // 创建一个可变字符串对象，用于拼接用户消息
        StringBuilder userMessage = new StringBuilder();
        // 拼接应用程序名称
        userMessage.append(app.getAppName()).append("\n");
        // 拼接应用程序描述
        userMessage.append(app.getAppDesc()).append("\n");
        // 创建一个问题答案 DTO 列表，用于存储问题和用户答案
        List<QuestionAnswerDTO> questionAnswerDTOList = new ArrayList<>();
        // 遍历问题内容 DTO 列表
        for (int i = 0; i < questionContentDTOList.size(); i++) {
            // 创建一个问题答案 DTO 对象
            QuestionAnswerDTO questionAnswerDTO = new QuestionAnswerDTO();
            // 设置问题标题
            questionAnswerDTO.setTitle(questionContentDTOList.get(i).getTitle());
            // 设置用户答案
            questionAnswerDTO.setUserAnswer(choices.get(i));
            // 将问题答案 DTO 添加到列表中
            questionAnswerDTOList.add(questionAnswerDTO);
        }
        // 将问题答案 DTO 列表转换为 JSON 字符串，并拼接到用户消息中
        userMessage.append(JSONUtil.toJsonStr(questionAnswerDTOList));
        // 返回构建的用户消息字符串
        return userMessage.toString();
    }


    /**
     * 构建缓存 key
     *
     * @param appId
     * @param choices
     * @return
     */
    private String buildCacheKey(Long appId, String choices) {
        return DigestUtil.md5Hex(appId + ":" + choices);
    }
}
